W85 — The Resort Tells You What Needs Attention - #361
Merged
Conversation
Builds system 19 (alerts): still-locked achievements unlock against post-resolution state first, then the three closed alert families — active incident, broken building, scenario resolved — derive from the current tick's own source set, raising once and clearing when a source goes inactive. Closes the world-graph tick-system gap 90-decisions.md has tracked since 2026-08-05 (W84 also gets its missed amendment). Widens the session store's achievement path matcher to accept world-graph's own member-scoped `unlockedAchievementIds.<id>.exists` alongside story-graph's flat `achieved.<id>` — 20-contract.md's §13 literally specifies the former, which the matcher never recognized. New replay fixture (world-graph-mvp-achievement) proves an achievement actually unlocks; every previously committed outcome is unchanged.
Two O(n^2)-shaped patterns in System 19's alerts() regressed against the Set-based fixes already established elsewhere in this codebase: the achievement-unlock loop used array .includes()/spread instead of a Set (story-graph/achievements.ts already fixed this exact pattern per PR #51), and the alert-clear loop remapped the whole alerts array once per clear instead of a single Set-driven pass (matching effects.ts's resolve_incident). Also names the two new visible:false reasons (alert_raised, alert_cleared) in reasons.ts's existing deliberately-unregistered list, so a later flip to visible:true isn't missed the way the identical defect was missed before.
The-Running-Dev
commented
Aug 21, 2026
The-Running-Dev
commented
Aug 21, 2026
The-Running-Dev
commented
Aug 21, 2026
The-Running-Dev
commented
Aug 21, 2026
The-Running-Dev
commented
Aug 21, 2026
The-Running-Dev
commented
Aug 21, 2026
The-Running-Dev
commented
Aug 21, 2026
design/20-contract.md's event-status table edit (achievement.unlocked and the two alert events moved from "specified, not yet delivered" to "delivered") needed the generation workflow run through: this regenerates docs/docs/engine/12-world-graph-kind.md and docs/docs/guide.md, and records build/Test-Documentation.ps1 now passing in verify-report.json (previously Failed, per code review on this PR).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #356. Builds system 19 (
alerts), the last of the five stub world-graph tick systems named indesign/90-decisions.md's 2026-08-05 known-and-retained entry.unlockedAchievementIds.<id>.existsunder the coreachievement_unlockedreason. Profile mirroring is the existing generic session-store mechanism (upsertAchievements), which only ever runs after a whole action succeeds — proved by a run whose refusedadvance_ticksleaves the profile untouched.world-graph.alert.<type>.title|messagebuilt-in strings; incident alerts reuse the incident definition's own name/description keys.achievement.unlocked,alert.raised,alert.clearedare declared and emitted; alert creation/removal audit rows are hidden (visible: false) per contract.advance_ticks nserializes byte-identically whether submitted whole or split, across a batch that both raises and clears an alert.world-graph-mvp-achievementreplay fixture proves a real unlock (double-cleaner, gated on hiring two cleaners — inert everywhere else, following the same gating pattern W84'sstormincident used). All previously committed outcomes are unchanged byte-for-byte.90-decisions.md's tick-system entry — it also picked up W84's missed amendment forincidents(system 16), so the entry now correctly reads zero systems remaining rather than jumping straight from "two remain" to a nonexistent state.A resolved contract-vs-code disagreement
20-contract.md§13 specifies the achievement audit path asunlockedAchievementIds.<id>.exists(world-graph's own member-scoped path shape), but the generic session-store matcher (achievementIdFrominsrc/engine/src/core/session/store.ts) only recognizedachieved.<id>(story-graph's flat convention). Implementing the contract literally would have silently broken profile mirroring for world-graph, with no test in this slice's own file likely to catch it. Resolved (user-confirmed) by widening the matcher to accept both prefixes — each kind keeps its own contract-literal path shape.Follow-up commits
d347ba5— code review fixes:alerts()'s achievement-unlock and alert-clear loops moved to Set-driven passes (matching the patternstory-graph/achievements.tsalready established), andreasons.ts's deliberately-unregistered list now namesalert_raised/alert_clearedalongside the othervisible: falsereasons.bfdf226— regenerateddocs/docs/engine/12-world-graph-kind.mdanddocs/docs/guide.mdfromdesign/20-contract.md's §12 edit, which the initial commit had made without carrying through the generation workflow.Touches
src/engine/src/kinds/world-graph/tick/pipeline.ts— system 19 (alerts)src/engine/src/kinds/world-graph/kind.ts—eventNamessrc/engine/src/kinds/world-graph/reasons.ts— kind-owned alert stringssrc/engine/src/campaigns/world-graph-mvp.ts— thedouble-cleanerachievementsrc/engine/src/core/session/store.ts— widened achievement path matchersrc/engine/fixtures/replay/design/20-contract.md,design/90-decisions.md— descriptive corrections (§12 event-table status rows for the three delivered events; the tick-system register closed out)docs/docs/engine/12-world-graph-kind.md,docs/docs/guide.md— regenerated from thedesign/20-contract.mdedit aboveVerified
Ran and passed:
npm --prefix src/engine run typecheck(tsc --noEmit && tsc --noEmit -p tsconfig.scripts.json) — exit 0npm --prefix src/engine run lint(eslint src scripts) — exit 0npm --prefix src/engine test(vitest run) — Test Files 79 passed (79), Tests 1146 passed (1146)npm pack --silentproducedthe-running-dev-game-engine-0.10.0.tgz, exit 0src/, notsconfig*.json, no.test.*build artifacts;package/dist/presentinstall:engine && build && smokeall exited 0*.ps1parsed with 0 errors./build/Test-Documentation.ps1: human-documentation drift check passed across 18 generated engine page(s), compatibility pointers, and the guide; documentation checks passed across 123 Markdown file(s)npm --prefix site run check(format:check, lint, typecheck, test, test:browser via Playwright, test:build, test:merge) — all passedDid not run locally (each has a corresponding CI check on this PR, all green —
Build, run, and smoke the image,Verify Documentation Build):docs-templatecontainer image;docs.ps1(the local wrapper) is absent from this checkout perCLAUDE.md(installer-generated, not committed).NUGET_GITHUB_TOKENto restoreSubZeroDev.Platform.Hostingfrom the sibling repository's GitHub Packages feed; not set locally.Actual PR checks (confirming the above):
enginepass,powershellpass,Documentation links and terminologypass,Verify Documentation Buildpass,Build, run, and smoke the imagepass.Test plan
pipeline.test.ts)kind.test.ts)world-graph-mvp-achievementfixture matches its committed outcome